﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using Microsoft.Practices.Unity;

namespace FluentUnity.Conventions
{
    /// <summary>
    /// Represents sets of conventions and is responsible for using those conventions to initialize Unity
    /// </summary>
    public class UnityConvention
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="UnityConvention"/> class.
        /// </summary>
        public UnityConvention()
        {
            Assemblies = new List<Assembly>();
            DependencyProperties = new DependencyPropertyScanConvention();
            Implementations = new ImplementationSelectionConvention();
            Interfaces = new InterfaceScanConvention();
            Lifetimes = new LifetimeSelectionConvention();
        }

        /// <summary>
        /// Gets the current UnityConventions.  Set only during call to <seealso cref="ApplyTo"/>
        /// </summary>
        public static UnityConvention Current { get; private set; }

        private List<Assembly> _Assemblies;

        /// <summary>
        /// Gets the assemblies.
        /// </summary>
        public IEnumerable<Assembly> Assemblies
        {
            get { return _Assemblies; }
            private set { _Assemblies = (List<Assembly>)value; }
        }

        private Dictionary<Type, Type> _Mappings;

        /// <summary>
        /// Gets the mappings.
        /// </summary>
        public IEnumerable<KeyValuePair<Type, Type>> Mappings
        {
            get { return _Mappings; }
            private set { _Mappings = value as Dictionary<Type, Type>; }
        }

        public delegate void LogHandler(string message);

        public event LogHandler Log;

        private void FireLog(string format, params object[] args)
        {
            if (Log != null)
                Log(String.Format(format, args));
        }

        public DependencyPropertyScanConvention DependencyProperties { get; private set; }
        public ImplementationSelectionConvention Implementations { get; private set; }
        public InterfaceScanConvention Interfaces { get; private set; }
        public LifetimeSelectionConvention Lifetimes { get; private set; }

        /// <summary>
        /// Adds the specified assembly.
        /// </summary>
        /// <param name="assembly">The assembly.</param>
        public void AddAssembly(Assembly assembly)
        {
            Ensure.IsNotNull(() => assembly);

            FireLog("AddAssembly({0})", assembly.GetName().Name);
            _Assemblies.Add(assembly);
        }

        /// <summary>
        /// Adds the assemblies.
        /// </summary>
        /// <param name="assemblies">The assemblies.</param>
        public void AddAssemblies(params Assembly[] assemblies)
        {
            Ensure.IsNotNull(() => assemblies);

            FireLog("AddAssemblies()");
            foreach (var assembly in assemblies)
            {
                AddAssembly(assembly);
            }
        }

        /// <summary>
        /// Applies this convention to the container.  Registering any mappings that match
        /// </summary>
        /// <param name="container">The container.</param>
        public void ApplyTo(IUnityContainer container)
        {
            FireLog("ApplyTo({0})", container);
            Ensure.IsNotNull(() => container);

            Current = this;

            try
            {
                FireLog("Applying conventions to assemblies: {0}", Assemblies.ToString(a => a.GetName().Name));

                MapInterfaces();

                MapImplementations();

                FireLog("Performing registration");
                //Perform registration
                foreach (var map in Mappings)
                {
                    RegisterMapping(container, map);
                }
            }
            finally
            {
                Current = null;
            }
        }

        protected virtual void MapInterfaces()
        {
            FireLog("Finding candidate interfaces");
            //Find interface candidates
            Mappings = Interfaces.Scan().ToDictionary(t => t);
        }

        protected virtual void MapImplementations()
        {
            FireLog("Finding implementations for candidate interfaces");
            //Find implementations for interface candidates and discard candidates with no implementation
            Mappings = (from @interface in _Mappings.Keys
                        let implementation = Implementations.Select(@interface)
                        where implementation != null
                        select new { @interface, implementation }).ToDictionary(m => m.@interface, m => m.implementation);
        }

        protected virtual void RegisterMapping(IUnityContainer container, KeyValuePair<Type, Type> map)
        {
            //Determine lifetime
            var lifetime = Lifetimes.Select(map.Key, map.Value);

            if (lifetime == null)
                FireLog("Registering from '{0}' to '{1}'", map.Key.Name, map.Value.Name);
            else
                FireLog("Registering from '{0}' to '{1}' with lifetime: {2}", map.Key.Name, map.Value.Name, lifetime);

            //Determine dependency properties
            var dependencies = DependencyProperties.Scan(map.Value).Cast<InjectionMember>().ToArray();

            //Register mapping
            if (lifetime != null)
                container.RegisterType(map.Key, map.Value, lifetime, dependencies);
            else
                container.RegisterType(map.Key, map.Value, dependencies);
        }
    }
}